package com.siteseat.common.deploy;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import com.siteseat.common.foundation.Component;
import com.siteseat.common.foundation.PropertyHelper;
import com.siteseat.common.security.Sha1Digest;

/**
 * @author yqiang
 */
public class FileDeployer extends Component implements Deployer {

  private DeployRepository  repository;

  private File folder;
  
  private Map<String, DeployEntry> erroredEntries = new HashMap<String, DeployEntry>();
  
  private boolean traceErrorDeployment = true;
  
  /**
   * Gets the repository
   *
   * @return the repository
   */
  public DeployRepository getRepository() {
    return repository;
  }

  /**
   * Sets the repository
   *
   * @param repository the repository to set
   */
  public void setRepository(DeployRepository repository) {
    this.repository = repository;
  }

  /**
   * Sets the folder
   *
   * @param folder the folder to set
   */
  public void setFolder(File folder) {
    this.folder = folder;
  }

  /**
   * 
   *
   * @return
   */
  public File getFolder() {
    return this.folder;
  }

  /**
   * 
   *
   * @param flder
   * @param toUndeploy
   * @param toDeploy
   * @param toRedeploy
   * @param toKeep
   */
  protected void categorizeDeployEntrys( File flder,
                                        Map<String, DeployEntry> toUndeploy,
                                        Map<String, DeployEntry> toDeploy,
                                        Map<String, DeployEntry> toRedeploy,
                                        Map<String, DeployEntry> toKeep ) {
    
    for( File f: flder.listFiles() ) {
      if( f.isDirectory() ) {
        categorizeDeployEntrys( f, toUndeploy, toDeploy, toRedeploy, toKeep );
        continue;
      } 
      
      String filePath;
      try {
        filePath = f.getCanonicalPath();
      } catch (Exception e) {
        log.error( "Error canocial path " + f.getAbsolutePath(), e );
        continue;
      }

      DeployEntry old = toUndeploy.remove( filePath );
      if( old == null ) {
        // new deployment
        try {
          old = new DeployEntry( f, filePath, Sha1Digest.digest(f) );
        } catch (Exception e) {
          log.error( "", e );
          continue;
        }
        toDeploy.put( filePath, old );
        continue;
      } 
      
      // existing deployment
      byte[] newDigest;
      try {
        newDigest = Sha1Digest.digest(f);
      } catch (Exception e) {
        log.error( "", e );
        continue;
      }
      if( false == old.isSameDigest( newDigest ) ) {
        // the file is changed so should reploy it
        old = new DeployEntry( f, filePath, newDigest, old.getData() );
        toRedeploy.put( filePath, old );
        continue;
      } 
      
      // keep it as it is
      toKeep.put( filePath, old );
    }
  }
  
  /**
   * {@inheritDoc}
   */
  @Override
  public void runDeployment() {
    Map<String, DeployEntry> entries = repository.getEntries();
    
    Map<String, DeployEntry> toKeep = new HashMap<String, DeployEntry>();    
    Map<String, DeployEntry> toUndeploy = new HashMap<String, DeployEntry>( entries );
    Map<String, DeployEntry> toDeploy = new HashMap<String, DeployEntry>();
    Map<String, DeployEntry> toRedeploy = new HashMap<String, DeployEntry>();
        
    categorizeDeployEntrys( getFolder(), toUndeploy, toDeploy, toRedeploy, toKeep );
    
    for( DeployEntry entry: toUndeploy.values() ) {
      undeploy( entry );
    }
    
    for( DeployEntry entry: toRedeploy.values() ) {
      redeploy( entry );
    }
    
    for( DeployEntry entry: toDeploy.values() ) {
      deploy( entry );
    }
  }

  /**
   * 
   *
   * @param entry
   * @return
   */
  protected boolean checkUnchangedErrorDeployment( DeployEntry entry ) {
    if( isTraceErrorDeployment() == false ) {
      return false;
    }
    String filePath = entry.getFilePath();
    DeployEntry oldErrored = erroredEntries.get( filePath );
    if( oldErrored != null ) {
      if( oldErrored.isSameDigest( entry.getDigest() ) ) {
        return true;
      }
      erroredEntries.remove( filePath );
    }
    return false;
  }
  
  /**
   * 
   *
   * @param entry
   */
  protected void addErrorDeployment( DeployEntry entry ) {
    if( isTraceErrorDeployment() == false ) {
      return;
    }
    erroredEntries.put( entry.getFilePath(), entry );
  }

  /**
   * 
   *
   * @param entry
   */
  protected void deploy( DeployEntry entry ) {
    String filePath = entry.getFilePath();
    
    log.info( "deploying " + filePath );
    
    boolean skip = checkUnchangedErrorDeployment( entry );
    if( skip ) {
      log.error( "skip " + filePath + " because it has error at last time and unchanged since then" );
      return;
    }
    
    try {
      repository.deploy( entry );
      log.info( "deployed " + filePath );
    } catch( Exception e ) {
      log.error( "Error deploying " + filePath, e );
      addErrorDeployment( entry );
    }
  }
  
  

  /**
   * 
   *
   * @param entry
   */
  protected void redeploy( DeployEntry entry ) {
    String filePath = entry.getFilePath();
    
    log.info( "re-deploying " + filePath );
    
    boolean skip = checkUnchangedErrorDeployment( entry );
    if( skip ) {
      log.error( "skip " + filePath + " because it has error at last time and unchanged since then" );
      return;
    }
    
    try {
      repository.redeploy( entry );
      log.info( "re-deployed " + filePath );
    } catch( Exception e ) {
      log.error( "Error redeploying " + filePath, e );
      addErrorDeployment( entry );
    }    
  }
  

  /**
   * 
   *
   * @param entry
   */
  protected void undeploy( DeployEntry entry ) {
    String filePath = entry.getFilePath();
    
    log.info( "undeploying " + filePath );
    
    boolean skip = checkUnchangedErrorDeployment( entry );
    if( skip ) {
      log.error( "skip " + filePath + " because it has error at last time and unchanged since then" );
      return;
    }

    try {
      repository.undeploy( entry );
      log.info( "undeployed " + filePath );
    } catch( Exception e ) {
      log.error( "Error undeploying " + filePath, e );
      addErrorDeployment( entry );
    }
  }

  /**
   * Gets the traceErrorDeployment
   *
   * @return the traceErrorDeployment
   */
  public boolean isTraceErrorDeployment() {
    return traceErrorDeployment;
  }

  /**
   * Sets the traceErrorDeployment
   *
   * @param traceErrorDeployment the traceErrorDeployment to set
   */
  public void setTraceErrorDeployment(boolean traceErrorDeployment) {
    this.traceErrorDeployment = traceErrorDeployment;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void doInit() throws Exception {
    PropertyHelper.ensureExists( log, getFolder(), "folder", true );
    PropertyHelper.ensureInitialized( log, getRepository(), "repository" );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void doDestroy() throws Exception {
    // nothing to do
  }
  
  
}
